#include "MainLayer.h"
#include "MainMenuScene.h"
#include "CShield.h"
#include "SimpleAudioEngine.h"
#include "CSoundPlayer.h"
#include "CRandom.h"
#include "Box2D/Box2D.h"
USING_NS_CC;

MainLayer::MainLayer():
	__pSpriteSheet(NULL),
	__pHudLayer(NULL),
	__pShip(NULL),
	__calibrationValues(),
	__winSize(),
	__zAccelArray(NULL),
	__zAccelArrayLength(0),
	__currentZAccelArrayIndex(0),
	__pEnemyGenerator(NULL),
	__pEnemies(NULL),
	__score(0),
	__pWorld(NULL),
	__autofire(false),
	_isDrawBoxDebug(false)
{}

MainLayer::~MainLayer()
{
	__pSpriteSheet->removeAllChildrenWithCleanup(true);
	delete __pShip;
	delete[] __zAccelArray;
	delete __pEnemyGenerator;
	delete __pEnemies;
	delete _debugDraw;
}


/**
 * @param calibrationValues This values are set when the player presses the "Start" button
 * 							and are passed to the layer when the MainScene is created.
 * @return true if the layer was initialized without problems, false otherwise.
 */
bool
MainLayer::init()
{
	if( !CCLayer::init())
	{
		return false;
	}

	// Initialize the array used in didAccelerate() callback
	// to determine if a barrel roll should be performed.
	__zAccelArrayLength = 5;
	__zAccelArray = new float[__zAccelArrayLength];
	__currentZAccelArrayIndex = 0;
	for(int i = 0; i < __zAccelArrayLength; i++)
	{
		__zAccelArray[i] = .0f;
	}

	__winSize = CCDirector::sharedDirector()->getWinSize();

	// Load and save sprite frames described in the MainScene.plist file.
	CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("MainScene.plist");

	// CCSpriteBatchNode will act as the sprite sheet.
	__pSpriteSheet = CCSpriteBatchNode::batchNodeWithFile("MainScene.png");
	this->addChild(__pSpriteSheet, -1);

	// Create the background sprite.
	CCSprite* pBackground = CCSprite::spriteWithSpriteFrameName("background.png");

	// Change the anchor for convenience.
	//pBackground->setAnchorPoint(ccp(0, 0));
	pBackground->setPosition(ccp(__winSize.width/2, __winSize.height/2));

	float scaleX = __winSize.width/ pBackground->getTextureRect().size.width;
	float scaleY = __winSize.height/ pBackground->getTextureRect().size.height;
	pBackground->setScaleX(scaleX);
	pBackground->setScaleY(scaleY);

	__pSpriteSheet->addChild(pBackground, -2);

	// Create the Box2D world.
	b2Vec2 gravity = b2Vec2(0.0f, 0.0f);
	__pWorld = new b2World(gravity);
	__pWorld->SetContactListener(this);
	// Debug Draw functions
	_debugDraw = new CDebugDrawGLES( P2M );
	__pWorld->SetDebugDraw(_debugDraw);
	uint32 flags = 0;
	flags += b2Draw::e_shapeBit;
	_debugDraw->SetFlags(flags);
	__pWorld->SetDebugDraw(_debugDraw);


	__autofire = true;

	scheduleUpdate();
	return true;
}

void
MainLayer::setHudLayer(HudLayer* pHudLayer)
{
	__pHudLayer = pHudLayer;
}

void
MainLayer::onEnter()
{
	CCLayer::onEnter();

	__pShip = new CShip();
	__pShip->init("shipRoll", __pSpriteSheet, __pWorld);

	__pEnemies = new CCArray();
	__pEnemyGenerator = new CEnemyGenerator();
	__pEnemyGenerator->init(__pEnemies, __pSpriteSheet, __pWorld);
	__pEnemyGenerator->startNextWave();
	this->setTouchEnabled(true);
	this->setAccelerometerEnabled(true);

	// Schedule autofire.
	schedule(schedule_selector(MainLayer::fire), __pShip->getBulletInterval());
	CSoundPlayer::sharedPlayer().playBackgroundMusic();
}

void
	MainLayer::draw()
{
	CCLayer::draw();
	if(!_isDrawBoxDebug)
		return;
	glDisable(GL_TEXTURE_2D);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	__pWorld->DrawDebugData();

	glEnable(GL_TEXTURE_2D);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}

void
MainLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
{
	/*
	// Get any touch and convert the touch position to in-game position.
	CCTouch* touch = (CCTouch*)pTouches->anyObject();
	CCPoint position = touch->locationInView();
	position = CCDirector::sharedDirector()->convertToGL(position);
	*/
	if(!__autofire)
	{
		__pShip->shoot();
	}
}

/**
 * Check if an enemy is off the screen and remove it if so.
 * Check if an enemy is colliding with the balloon and end the game if that's the case.
 * Change the scene if the game has ended.
 */
void
MainLayer::update(float dt)
{
    __pWorld->Step(1.0f / 60.0f, 8, 8);
	__pShip->cleanBullets();
	__pShip->cleanShield();
	updateEnemies();
	updateB2Bodies();

	if(__pShip->getCurrentHealthPoints() <= 0)
	{
		endGame();
	}
}

/**
 * The method iterates through bodies defined in the Box2D world and updates the position of the objects.
 */
void
MainLayer::updateB2Bodies()
{
    for(b2Body *pBody = __pWorld->GetBodyList(); pBody; pBody = pBody->GetNext())
    {
    	CAnimatedObject* pAnimatedObject = (CAnimatedObject*)pBody->GetUserData();
    	int objectTag = pAnimatedObject->getTag();
    	if( (objectTag == MainLayer::shipTag) ||
    		(objectTag == MainLayer::shieldTag) ||
    		(objectTag == MainLayer::enemyTag))
    	{
    		// Bodies of these objects need to be updated according to sprite positions related with the bodies.
			b2Vec2 b2Position = b2Vec2(pAnimatedObject->getPosition().x / P2M,
									   pAnimatedObject->getPosition().y / P2M);
			float32 b2Angle = -1 * CC_DEGREES_TO_RADIANS(pAnimatedObject->getRotation());

			if(pAnimatedObject->getTag() == MainLayer::shieldTag)
			{
				CCPoint anchor = pAnimatedObject->getAnchorPoint();
				b2Position.x += pAnimatedObject->boundingBox().size.width * (0.5f - anchor.x) / P2M;
			}

			pBody->SetTransform(b2Position, b2Angle);
    	}
    	else if	(objectTag == MainLayer::bulletTag)
    	{
    		// Sprite position of the bullet object needs to be updated according to the body position.
   			pAnimatedObject->setPosition( CCPointMake( pBody->GetPosition().x * P2M, pBody->GetPosition().y * P2M) );
			pAnimatedObject->setRotation( -1 * CC_RADIANS_TO_DEGREES(pBody->GetAngle()) );

			CBullet* bullet = static_cast<CBullet*>(pAnimatedObject);
			CBulletParams::EBulletType bulletType = bullet->getType();

			if(bulletType == CBulletParams::bomb)
			{
				// Apply a gravity force to the bullet.
				b2Vec2 force(0.0f, -2.0f);
				pBody->ApplyForceToCenter(force);
			}
    	}
    }
}

/**
 * If the __score value was updated elsewhere,
 * call this method without the points parameter to refresh the score label.
 */
void
MainLayer::updateScore(float points)
{
	__score += points;
	__pHudLayer->updateScore(static_cast<int>(__score));
}

void
MainLayer::updateHealthPoints(float points)
{
	__pHudLayer->updateHealth(static_cast<int>(points));
}

void
MainLayer::updateShieldPoints(float points)
{
	__pHudLayer->updateShield(static_cast<int>(points));
}

/**
 * Check __pEnemies for enemies that should be removed and release them.
 */
void
MainLayer::cleanEnemies()
{
	CCArray* pEnemiesToRemove = new CCArray();
	CCObject* enemyIter;
	CCARRAY_FOREACH(__pEnemies, enemyIter)
	{
		CEnemy* pEnemy = (CEnemy*)enemyIter;
		if(!pEnemy)
		{
			break;
		}

        bool removeEnemy = pEnemy->getDelete();
        if(removeEnemy)
        {
        	pEnemy->destroy();
        	pEnemiesToRemove->addObject(pEnemy);
        }
	}

    __pEnemies->removeObjectsInArray(pEnemiesToRemove);
    delete pEnemiesToRemove;
}

/**
 * Update enemies if they are off screen or scheduled for deletion (cleanEnemies).
 */
void
MainLayer::updateEnemies()
{
	cleanEnemies();
	if(__pEnemies && __pEnemies->count() > 0)
	{
		CCObject* enemyIter;
		CCARRAY_FOREACH(__pEnemies,enemyIter)
		{
			CEnemy* pEnemy = (CEnemy*)enemyIter;

			if(!pEnemy)
			{
				break;
			}

			CCRect winRect = CCRect(0.0f, 0.0f, __winSize.width + 100, __winSize.height);
			CCRect enemyRect = pEnemy->getRect();
			if(!CCRect::CCRectIntersectsRect(winRect, enemyRect))
			{
				// The enemy is off screen.
				pEnemy->setDelete(true);
			}
		}
		cleanEnemies();
	}
}

void
MainLayer::didAccelerate(CCAcceleration* pAccelerationValue)
{
	if(__pShip)
	{
		CCPoint move(0.0f, 0.0f);
		b2Vec3 ax(1.0f, 0.0f, 0.0f);
		b2Vec3 ay(__calibrationValues.x, __calibrationValues.y, __calibrationValues.z);
		float ayLength = b2Sqrt(ay.x * ay.x + ay.y * ay.y + ay.z * ay.z);
		ay.x = ay.x / ayLength;
		ay.y = ay.y / ayLength;
		ay.z = ay.z / ayLength;

		b2Vec3 az = b2Cross(ay, ax);

		ax = b2Cross(az, ay);

		// Calculate the move vector
		move.x = b2Dot(b2Vec3(pAccelerationValue->x, pAccelerationValue->y, pAccelerationValue->z), ax);
		move.y = b2Dot(b2Vec3(pAccelerationValue->x, pAccelerationValue->y, pAccelerationValue->z), az);

		// Check if the Z acceleration value is enough to do a barrel roll
		ay = b2Cross(az, ax);
		float z = b2Dot(b2Vec3(pAccelerationValue->x, pAccelerationValue->y, pAccelerationValue->z), ay);

		float avgZ = getAvgZAccel();
		if(abs(avgZ - z) > 0.4f)
		{
			__pShip->doABarrelRoll();
		}
		else
		{
			__zAccelArray[__currentZAccelArrayIndex] = z;

			__currentZAccelArrayIndex++;
			if(__currentZAccelArrayIndex > 4)
			{
				__currentZAccelArrayIndex = 0;
			}
		}
		__pShip->setMove(move);
	}
}

/**
 * @return The average value of __zAccelArray elements.
 */
float
MainLayer::getAvgZAccel()
{
	float sum = 0;

	for(int i = 0; i < __zAccelArrayLength; i++)
	{
		sum += __zAccelArray[i];
	}

	return sum / __zAccelArrayLength;
}

void
MainLayer::endGame()
{
	// Get back to the MenuScene.
	CCScene *pScene = MainMenuScene::scene();
	CCDirector::sharedDirector()->replaceScene(pScene);
}

/**
 * This method will call collision() method on colliding objects (mainly on Bullets).
 */
void
MainLayer::BeginContact(b2Contact* pContact)
{
	CAnimatedObject* pAnimatedObject1 = (CAnimatedObject*)pContact->GetFixtureA()->GetBody()->GetUserData();	
	CAnimatedObject* pAnimatedObject2 = (CAnimatedObject*)pContact->GetFixtureB()->GetBody()->GetUserData();
	if(NotCheckCollision(pAnimatedObject1,pAnimatedObject2))
		return;
	pAnimatedObject1->collision();
	pAnimatedObject2->collision();
}

void
MainLayer::EndContact(b2Contact* pContact)
{

}

void
MainLayer::PreSolve(b2Contact* pContact, const b2Manifold* pOldManifold)
{

}

/**
 * Calculate damage based on collision impulse value and call collision(damage) method on colliding objects.
 */
void
MainLayer::PostSolve(b2Contact* pContact, const b2ContactImpulse* pImpulse)
{
	//nhandt dame of each colision
	float dmgMagnitude = 200.0f;
	float impulse = pImpulse->normalImpulses[0] + pImpulse->normalImpulses[1];
	if(impulse<=0) 
		return;
	b2Body* body = pContact->GetFixtureA()->GetBody();
	CAnimatedObject* pAnimatedObject1 = static_cast<CAnimatedObject*>(body->GetUserData());

	body = pContact->GetFixtureB()->GetBody();
	CAnimatedObject* pAnimatedObject2 = static_cast<CAnimatedObject*>(body->GetUserData());
	if(NotCheckCollision(pAnimatedObject1,pAnimatedObject2))
		return;

	float mass = body->GetMass();
	float damage = (impulse / mass) * dmgMagnitude;
	pAnimatedObject1->collision(damage);
	
	mass = body->GetMass();
	damage = (impulse / mass) * dmgMagnitude;
	pAnimatedObject2->collision(damage);
}

void
MainLayer::toggleAutofire()
{
	__autofire = !__autofire;
	if(__autofire)
	{
		schedule(schedule_selector(MainLayer::fire), __pShip->getBulletInterval());
	}
	else
	{
		unschedule(schedule_selector(MainLayer::fire));
	}
}

void
MainLayer::changeDrawDebugMode()
{
	_isDrawBoxDebug = !_isDrawBoxDebug;
}
void
MainLayer::changeGun()
{
	__pShip->changeGunType();
	if(__autofire)
	{
		unschedule(schedule_selector(MainLayer::fire));
		schedule(schedule_selector(MainLayer::fire), __pShip->getBulletInterval());
	}
}

void
MainLayer::fire(float dt)
{
	__pShip->shoot();
}

bool
MainLayer::NotCheckCollision(CAnimatedObject* obj1,CAnimatedObject* obj2)
{
	int objectTag1 = obj1->getTag();
	int objectTag2 = obj2->getTag();
	if(objectTag1 == MainLayer::bulletTag && objectTag2 == MainLayer::bulletTag)
	{
		return true;
	}
	else if( objectTag1 == MainLayer::bulletTag && objectTag2 == MainLayer::shipTag)
	{
		CBullet* bullet = (CBullet*) obj1;
		return bullet->_bulletParams._fireFromMC;
	}
	else if( objectTag2 == MainLayer::bulletTag && objectTag1 == MainLayer::shipTag)
	{
		CBullet* bullet = (CBullet*) obj2;
		return bullet->_bulletParams._fireFromMC;
	}
	return false;
}